package com.hwd.srb.core.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hwd.srb.common.constant.DictConstants;
import com.hwd.srb.core.listener.ExcelDictDTOListener;
import com.hwd.srb.core.mapper.DictMapper;
import com.hwd.srb.core.pojo.dto.ExcelDictDTO;
import com.hwd.srb.core.pojo.entity.Dict;
import com.hwd.srb.core.service.DictService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 数据字典 服务实现类
 * </p>
 *
 * @author 黄伟东/Victor
 * @since 2022-03-10
 */
@Service
@Slf4j
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {

    /**
     * 设置redis缓存默认过去时间为5分钟
     */
    private static final long TIMEOUT_MILLISECONDS = 5 * 60 * 1000;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 导入文件
     *
     * @param multipartFile 导入的文件对象
     */
    @Override
    public void importData(MultipartFile multipartFile) throws IOException {

        // 调用easyExcel的读方法，通过监听将上传的excel数据导入数据库
        EasyExcel.read(multipartFile.getInputStream(), ExcelDictDTO.class, new ExcelDictDTOListener(baseMapper))
                .sheet().doRead();
    }

    /**
     * 查询ExcelDictDTO列表信息
     */
    @Override
    public List<ExcelDictDTO> listDictData() {
        List<Dict> dictList = baseMapper.selectList(null);
        return dictList.stream().map(DictServiceImpl::apply).collect(Collectors.toList());
    }

    /**
     * 根据数据字典上级id获取子节点数据列表
     *
     * @param parentId 上级节点id
     * @return java.util.List<java.util.Map < java.lang.String, java.lang.Object>>
     */
    @Override
    public List<Dict> listByParentId(Long parentId) {

        List<Dict> dictList;
        // 生成存入redis中的key
        String cacheKey = String.format(DictConstants.REDIS_DICT_LIST_KEY_FORMAT, parentId);

        // 先查询缓存有没有数据
        log.info("正在从缓存中查询 " + cacheKey + " 的数据字典列表");
        dictList = (List<Dict>) redisTemplate.opsForValue().get(cacheKey);

        // 没有缓存，查询数据库
        if (dictList == null) {
            dictList = this.getDictListFromDB(parentId, cacheKey);
        }

        // 判断该节点是否有子节点
        if (dictList != null) {
            dictList.forEach(dict -> dict.setHasChildren(this.hasChildren(dict.getId())));
        }

        return dictList;
    }

    /**
     * 根据dictCode查询字典集合
     *
     * @param dictCode dictCode
     * @return java.util.List<com.hwd.srb.core.pojo.entity.Dict>
     */
    @Override
    public List<Dict> findByDictCode(String dictCode) {
        List<Dict> dictList = new ArrayList<>();
        // 根据dictCode获取parentId
        LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<Dict>()
                .select(Dict::getId)
                .eq(Dict::getDictCode, dictCode);
        List<Object> list = baseMapper.selectObjs(queryWrapper);
        if (list != null && list.size() > 0) {
            // 获取到获取parentId
            dictList = listByParentId((Long) list.get(0));
        }

        return dictList;
    }

    /**
     * 根据dictCode和value值获取dict表中name值
     *
     * @param dictCode dict表dictCode
     * @param value    dict表的value值
     * @return java.lang.String
     */
    @Override
    public String getNameByParentDictCodeAndValue(String dictCode, Integer value) {

        // 获取dictCode对应的id
        LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<Dict>()
                .select(Dict::getId)
                .eq(Dict::getDictCode, dictCode);
        Dict dictParent = baseMapper.selectOne(queryWrapper);

        // 根据父id查询name值
        queryWrapper = new LambdaQueryWrapper<Dict>()
                .select(Dict::getName)
                .eq(Dict::getParentId, dictParent.getId())
                .eq(Dict::getValue,value);
        Dict dict = baseMapper.selectOne(queryWrapper);

        return dict.getName();
    }

    /**
     * 从数据库中查询数据字典列表，并缓存到redis中
     *
     * @param parentId 父id
     * @param cacheKey 数据字典列表同步到缓存中的key
     * @return java.util.List<com.hwd.srb.core.pojo.com.hwd.srb.jwt.entity.Dict>
     */
    private List<Dict> getDictListFromDB(Long parentId, String cacheKey) {

        List<Dict> dictList;
        log.info("缓存中没有 " + cacheKey + " 的数据字典列表，正在查询数据库");
        dictList = baseMapper.selectList(new LambdaQueryWrapper<Dict>().eq(Dict::getParentId, parentId));

        if (dictList != null && dictList.size() > 0) {
            // 数据库中有数据，同步到缓存中
            log.info("正在往缓存中同步 " + cacheKey + " 的数据字典列表");
            redisTemplate.opsForValue().set(cacheKey, dictList);

        } else {
            // 数据库中没有数据，向缓存中存null，并设置过期时间，防止利用缓存穿透恶意攻击 TODO
            redisTemplate.opsForValue().set(cacheKey, new ArrayList<Dict>(), TIMEOUT_MILLISECONDS);
        }

        return dictList;
    }

    /**
     * 把dict对象封装成excelDictDTO
     *
     * @param dict 数据字典信息
     * @return com.hwd.srb.core.pojo.dto.ExcelDictDTO
     */
    private static ExcelDictDTO apply(Dict dict) {
        ExcelDictDTO excelDictDTO = new ExcelDictDTO();
        BeanUtils.copyProperties(dict, excelDictDTO);
        return excelDictDTO;
    }

    /**
     * 判断该节点是否有子节点，判断当前id是否有被其他数据当做做父id
     *
     * @param id 节点id
     */
    private boolean hasChildren(Long id) {
        boolean hasChildren = false;
        Integer count;

        // 生成存入redis中的key
        String cacheKey = String.format(DictConstants.REDIS_COUNT__BY_PARENT_ID_KEY_FORMAT, id);

        // 先查询缓存是否有数据
        log.info("正在从缓存中查询 " + cacheKey + " 的数据");
        count = (Integer) redisTemplate.opsForValue().get(cacheKey);

        if (count == null) {
            // 缓存中没有数据，查询数据库
            log.info("缓存中没有 " + cacheKey + " 的数据，正在查询数据库");
            count = baseMapper.selectCount(new LambdaQueryWrapper<Dict>().eq(Dict::getParentId, id));

            // 同步缓存数据
            log.info("正在同步缓存中 " + cacheKey + " 的数据");
            redisTemplate.opsForValue().set(cacheKey, count);
        }

        if (count > 0) {
            hasChildren = true;
        }
        return hasChildren;
    }
}
